// Misc functions module, modified by regret

/* changelog:
 update 2: add sepia color
 update 1: add color adjust (ref: bsnes)
*/

#include "burner.h"
#include "misc.h"

// ----------------------------------------------------------------------------
// Software gamma, brightness and contrast adjust, added by regret
// ref: bsnes

int colorAdjustEnable = 0;
int color_gamma = 0;
int color_brightness = 0;
int color_contrast = 0;
int color_grayscale = 0;
int color_invert = 0;
int color_sepia = 0;

static inline unsigned char contrast_adjust(unsigned char input)
{
	signed result = input - color_contrast + (2 * color_contrast * input + 127) / 255;
	return max(0, min(255, result));
}

static inline unsigned char brightness_adjust(unsigned char input)
{
	signed result = input + color_brightness;
	return max(0, min(255, result));
}

static inline unsigned char gamma_adjust(unsigned char input)
{
	signed result = (signed)(pow(((double)input / 255.0), (double)(100 + color_gamma) / 100.0) * 255.0 + 0.5);
	return max(0, min(255, result));
}

void colorAdjust(int& r, int& g, int& b)
{
	if (!colorAdjustEnable) {
		return;
	}

	double kr = 0.2126, kb = 0.0722, kg = (1.0 - kr - kb);  //luminance weights

	if (color_contrast != 0) {
		r = contrast_adjust(r);
		g = contrast_adjust(g);
		b = contrast_adjust(b);
	}

	if (color_brightness != 0) {
		r = brightness_adjust(r);
		g = brightness_adjust(g);
		b = brightness_adjust(b);
	}

	if (color_gamma != 0) {
		r = gamma_adjust(r);
		g = gamma_adjust(g);
		b = gamma_adjust(b);
	}

	if (color_sepia != 0) {
		signed l = (signed)((double)r * kr + (double)g * kg + (double)b * kb);
		l = max(0, min(255, l));

		r = (signed)((double)l * (1.0 + 0.300));
		g = (signed)((double)l * (1.0 - 0.055));
		b = (signed)((double)l * (1.0 - 0.225));

		r = max(0, min(255, r));
		g = max(0, min(255, g));
		b = max(0, min(255, b));
	}

	if (color_grayscale) {
		signed l = (signed)((double)r * kr + (double)g * kg + (double)b * kb);
		l = max(0, min(255, l));
		r = g = b = l;
    }

    if (color_invert) {
		r ^= 0xff;
		g ^= 0xff;
		b ^= 0xff;
    }
}

// ----------------------------------------------------------------------------
unsigned int (__cdecl *vidHighCol) (int r, int g, int b, int i) = NULL;

// Standard callbacks for 16/24/32 bit color:
unsigned int __cdecl highCol15(int r, int g, int b, int  /* i */)
{
	colorAdjust(r, g, b);

	unsigned int t;
	t  = (r << 7) & 0x7c00; // 0rrr rr00 0000 0000
	t |= (g << 2) & 0x03e0; // 0000 00gg ggg0 0000
	t |= (b >> 3) & 0x001f; // 0000 0000 000b bbbb
	return t;
}

// 24-bit/32-bit
unsigned int __cdecl highCol24(int r, int g, int b, int  /* i */)
{
	colorAdjust(r, g, b);

	unsigned int t;
	t  = (r << 16) & 0xff0000;
	t |= (g << 8 ) & 0x00ff00;
	t |= (b      ) & 0x0000ff;
	return t;
}

int setBurnHighCol()
{
	videoInterface.recalcPal();

	vidHighCol = highCol24;

	if (driverOkay && !(BurnDrvGetFlags() & BDF_16BIT_ONLY)) {
		BurnHighCol = vidHighCol;
	}

	return 0;
}

void setColorFunc()
{
	BurnHighCol = highCol15;
}

void recalcPalette(unsigned int* pd)
{
	if (!pd || !vidHighCol)
		return;

	for (int r = 0; r < 256; r += 8) {
		for (int g = 0; g < 256; g += 8) {
			for (int b = 0; b < 256; b += 8) {
				pd[(r << 7) | (g << 2) | (b >> 3)] = vidHighCol(r | (r >> 5), g | (g >> 5), b | (b >> 5), 0);
			}
		}
	}
}

void calcColor(unsigned char* pd, const unsigned char* ps, int width, int height)
{
	if (!pd || !ps || !vidHighCol)
		return;

	for (int x = 0; x < width; x++, ps += 3) {
		unsigned int color = vidHighCol(ps[2], ps[1], ps[0], 0);
		*((unsigned int*)pd) = color;
		pd += 4;
	}
}
